package com.zen.lon.open.proxy;

import static org.springframework.core.annotation.AnnotatedElementUtils.findMergedAnnotation;

import com.zen.lon.open.remote.exp.RemoteClientException;
import com.zen.lon.open.remote.utils.ClassProviderUtils;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * 代理调用方法处理器实现
 *
 * @author ZonLen since on 2021/7/12 下午1:33
 */
public class ProxyAgentMethod implements InvocationHandler {

  private static final Map<String, ProxyAgentMethodInvoker> PROXY_METHOD_CACHE = new ConcurrentHashMap<>();

  private final Class<?> proxyClass;

  public <P> ProxyAgentMethod(Class<P> proxyClass) {
    this.proxyClass = proxyClass;
    for (Method method : proxyClass.getMethods()) {
      final RequestMapping requestMapping = findMergedAnnotation(method, RequestMapping.class);
      if (requestMapping != null) {
        initProxyMethodInvoker(method);
      }
    }
  }

  /**
   * 执行调用
   */
  @Override
  @SuppressWarnings("all")
  public Object invoke(Object proxy, Method method, Object[] args) {
    ProxyAgentMethodInvoker proxyAgentMethodInvoker = PROXY_METHOD_CACHE
        .get(ClassProviderUtils.functionFlag(proxyClass, method));
    if ("toString".equals(method.getName())) {
      return proxyAgentMethodInvoker.getClass().getName();
    }
    if ("hashCode".equals(method.getName())) {
      return "proxy class invoke hashCode method";
    }
    return proxyAgentMethodInvoker.invoke(proxyClass, method, args);
  }

  /**
   * 基于反射初始化方法代理
   */
  private void initProxyMethodInvoker(
      Method method) {
    final String key = ClassProviderUtils.functionFlag(proxyClass, method);
    synchronized (this) {
      if (PROXY_METHOD_CACHE.containsKey(key)) {
        return;
      }
    }
    final ProxyAgent proxyAgent = findMergedAnnotation(proxyClass, ProxyAgent.class);
    if (null == proxyAgent) {
      throw new RemoteClientException("@ProxyObj annotation do not find, please check");
    }
    final Class<? extends ProxyAgentMethodInvoker> proxyMethodInvokerClass = proxyAgent
        .proxyMethodInvoker();
    if (proxyMethodInvokerClass.isInterface()) {
      throw new RemoteClientException(
          "ProxyMethodInvoker is abstract, please impl a class can instance");
    }
    try {
      final ProxyAgentMethodInvoker proxyAgentMethodInvoker = proxyMethodInvokerClass.newInstance();
      PROXY_METHOD_CACHE.putIfAbsent(key, proxyAgentMethodInvoker);
    } catch (InstantiationException | IllegalAccessException e) {
      throw new RemoteClientException("new ProxyMethodInvoker instance exp:\n " + e.getCause());
    }

  }
}
